package com.idea.easy.log.translator;

import java.util.Set;
import javax.servlet.Servlet;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import com.idea.easy.log.event.ErrorLogEvent;
import com.idea.easy.log.model.ErrorLogModel;
import com.idea.easy.log.publisher.ErrorLogPublisher;
import com.idea.easy.log.utils.result.R;
import com.idea.easy.log.utils.result.RCode;
import com.idea.easy.log.utils.str.StrUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * @className: RestExceptionTranslator
 * @description: 全局异常处理类
 * @author: salad
 * @date: 2022/6/1
 **/
@Slf4j
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnProperty(
        prefix = "easy-log",
        name = {"enabled","error-processor"},
        havingValue = "true"
)
@RestControllerAdvice
public class RestExceptionTranslator{

    @ExceptionHandler({Throwable.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R handleError(Throwable e) {
        log.error(RCode.SERVER_ERR.getMessage(), e);
        ErrorLogPublisher.event(new ErrorLogEvent(new ErrorLogModel()),e).publish();
        return R.fail(StrUtils.isBlank(e.getMessage()) ? RCode.SERVER_ERR.getMessage() : e.getMessage());
    }

    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(MissingServletRequestParameterException e) {
        log.warn(RCode.PARAMS_MISS.getMessage(), e.getMessage());
        return R.fail(RCode.PARAMS_MISS, String.format(RCode.PARAMS_MISS.getMessage() + ": %s", e.getParameterName()));
    }

    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(MethodArgumentTypeMismatchException e) {
        log.warn(RCode.PARAM_FORMAT_ERROR.getMessage(), e.getMessage());
        return R.fail(RCode.PARAM_FORMAT_ERROR, String.format(RCode.PARAM_FORMAT_ERROR.getMessage() + ": %s", e.getName()));
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(MethodArgumentNotValidException e) {
        return this.handleBindError(RCode.PARAM_VALID_ERROR,e.getBindingResult(),e);
    }

    @ExceptionHandler({BindException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(BindException e) {
        return this.handleBindError(RCode.PARAM_BIND_ERROR,e.getBindingResult(),e);
    }

    private R handleBindError(RCode code,BindingResult result,BindException e) {
        log.warn(code.getMessage(), e.getMessage());
        FieldError error = result.getFieldError();
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return R.fail(RCode.PARAM_BIND_ERROR, message);
    }

    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(ConstraintViolationException e) {
        log.warn(RCode.PARAM_VALID_ERROR.getMessage(), e.getMessage());
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        return R.fail(RCode.PARAM_VALID_ERROR, violation.getMessage());
    }

    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(HttpMessageNotReadableException e) {
        log.error(RCode.MSG_NOT_READABLE.getMessage() + " {}", e.getMessage());
        return R.fail(RCode.MSG_NOT_READABLE, e.getMessage());
    }

    @ExceptionHandler({NoHandlerFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public R handleError(NoHandlerFoundException e) {
        log.error(RCode.NOT_FOUND.getMessage() + " {}", e.getMessage());
        return R.fail(RCode.NOT_FOUND, e.getMessage());
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public R handleError(HttpRequestMethodNotSupportedException e) {
        log.error(RCode.METHOD_NOT_SUPPORTED.getMessage() + " {}", e.getMessage());
        return R.fail(RCode.METHOD_NOT_SUPPORTED, e.getMessage());
    }

    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public R handleError(HttpMediaTypeNotSupportedException e) {
        log.error(RCode.MEDIA_TYPE_NOT_SUPPORTED.getMessage() + " {}", e.getMessage());
        return R.fail(RCode.MEDIA_TYPE_NOT_SUPPORTED, e.getMessage());
    }

    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public R handleError(HttpMediaTypeNotAcceptableException e) {
        String message = e.getMessage() + " : " + StrUtils.join(e.getSupportedMediaTypes());
        log.error(RCode.MEDIA_TYPE_NOT_SUPPORTED.getMessage() + " {}", message);
        return R.fail(RCode.MEDIA_TYPE_NOT_SUPPORTED, message);
    }
}
